#include "triangle.h"

#include "geometry/ray.h"
#include "geometry/diff_geometry.h"
#include "geometry/aabb.h"

namespace venus {
  AABB Triangle::Bound() const {
    AABB bound(this->point(0), this->point(1));
    bound.Union(this->point(2));
    return bound;
  }

  int Triangle::Intersect(const Ray &ray, float *thit,
      DifferentialGeometry *dg) const {
    const Point &a = this->point(0);
    const Point &b = this->point(1);
    const Point &c = this->point(2);
    const Vector3 &edge_ab = b - a;
    const Vector3 &edge_ac = c - a;

    const Vector3 &s1 = Cross(ray.d, edge_ac);
    const float det = Dot(edge_ab, s1);
    if (det == 0) return 0;

    const Vector3 &d = ray.o - a;
    Vector3 s2;
    float b1, b2;
    if (det > 0) {
      b1 = Dot(d, s1);
      if (b1 < 0 || b1 > det) return 0;

      s2 = Cross(d, edge_ab);
      b2 = Dot(ray.d, s2);
      if (b2 < 0 || b1 + b2 > det) return 0;
    }
    else {
      b1 = Dot(d, s1);
      if (b1 > 0 || b1 < det) return 0;

      s2 = Cross(d, edge_ab);
      b2 = Dot(ray.d, s2);
      if (b2 > 0 || b1 + b2 < det) return 0;
    }

    float inv_det = 1.f / det;
    float dist = Dot(edge_ac, s2);
    dist *= inv_det;
    if (dist <= ray.tmin || dist >= ray.tmax + EPSILON) return 0;
    *thit = dist;
    b1 *= inv_det; b2 *= inv_det;

    //compute differential geometry
    /*float U0 = this->GetVertex( 0 ).GetU();
    float V0 = this->GetVertex( 0 ).GetV();
    float U1 = this->GetVertex( 1 ).GetU();
    float V1 = this->GetVertex( 1 ).GetV();
    float U2 = this->GetVertex( 2 ).GetU();
    float V2 = this->GetVertex( 2 ).GetV();*/

    //the texture coordinate of the hit point, computed by interpolation
    /*float tu = U0 + beta * ( U1 - U0 ) + gamma * ( U2 - U0 );
    float tv = V0 + beta * ( V1 - V0 ) + gamma * ( V2 - V0 );*/

    const Normal &N0 = this->normal(0);
    const Normal &N1 = this->normal(1);
    const Normal &N2 = this->normal(2);
    const Normal& N = N0 + b1 * (N1 - N0) + b2 * (N2 - N0);

    const Point& hitPoint = ray.o + ray.d * dist;
    *dg = DifferentialGeometry(hitPoint, Normalize(N), 0, 0, this);

    return HIT;
  }

  int Triangle::Intersect(const Ray &ray) const {
    const Point &a = this->point(0);
    const Point &b = this->point(1);
    const Point &c = this->point(2);
    const Vector3 &edge_ab = b - a;
    const Vector3 &edge_ac = c - a;

    const Vector3 &s1 = Cross(ray.d, edge_ac);
    const float det = Dot(edge_ab, s1);
    if (det == 0) return 0;

    const Vector3 &d = ray.o - a;
    Vector3 s2;
    float b1, b2;
    if (det > 0) {
      b1 = Dot(d, s1);
      if (b1 < 0 || b1 > det) return 0;

      s2 = Cross(d, edge_ab);
      b2 = Dot(ray.d, s2);
      if (b2 < 0 || b1 + b2 > det) return 0;
    }
    else {
      b1 = Dot(d, s1);
      if (b1 > 0 || b1 < det) return 0;

      s2 = Cross(d, edge_ab);
      b2 = Dot(ray.d, s2);
      if (b2 > 0 || b1 + b2 < det) return 0;
    }

    float inv_det = 1.f / det;
    float dist = Dot(edge_ac, s2);
    dist *= inv_det;
    if (dist <= ray.tmin || dist >= ray.tmax + EPSILON) return 0;
    return HIT;
  }
}//namespace venus

